﻿/*
 * PegImage Class
 * Karim Sultan (karimsultan@hotmail.com)
 * October 2009
 * 
 * This class represents the image data as drawn in PegEdit and
 * compatible with Windell Oskay's crafty Peggy2.
 * 
 * Free software under LGPL 2.1.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PegEdit
{
   /*
    * Peggy2Image class
    * Karim Sultan Oct 31 2009
    * 
    * This class abstracts the image management of a Peggy2Immage from
    * applications like PegEdit.
    */

   class PegImage
   {
      // Height and width are fixed (for now anyways)
      public const int MAX_WIDTH = 25;
      public const int MAX_HEIGHT = 25;

      // Grey scale levels (16 ranging 0 to 15)
      public const int MAX_LEVELS = 16;

      // Empty color is 0 (black /off)
      public const int EMPTY_CELL = 0;

      // The image data
      byte[,] image = new byte[25, 25];

      // Types of output formats for overloaded ToString()
      public enum FORMAT_TYPE
      {
         IMAGE_RAW,
         IMAGE_PLANAR,
         IMAGE_BASE64
      };

      // Private setters for now, until resizing operation is permitted
      public int Width { get; private set; }
      public int Height { get; private set; }

      /*
       * 
       * PUBLIC METHODS
       * 
       */


      /// <summary>
      /// Constructor for class.
      /// </summary>
      public PegImage()
      {
         // Zero image
         Clear();

         // Set height and width to fixed value, until resizing is permitted.
         // Then add overloaded constructor taking width, height parameters
         this.Width = 25;
         this.Height = 25;
      }


      /// <summary>
      /// Initializes the array to EMPTY_CELL (blank).
      /// </summary>
      public void Clear()
      {
         // Setup the array to all empty
         for (int row = 0; row < MAX_HEIGHT; row++)
            for (int col = 0; col < MAX_WIDTH; col++)
               image[row, col] = EMPTY_CELL;
      }


      /// <summary>
      /// Sets a point in the array (x,y) to color index (c).
      /// </summary>
      /// <param name="x">x position (column)</param>
      /// <param name="y">y position (row)</param>
      /// <param name="c">color index (greyscale)</param>
      public void SetPoint(int x, int y, int c)
      {
         // Validate - don't throw exception or fail; just constrain
         if (x < 0) x = 0;
         else if (x >= MAX_WIDTH) x = MAX_WIDTH - 1;

         if (y < 0) y = 0;
         else if (y >= MAX_HEIGHT) y = MAX_HEIGHT - 1;

         if (c < 0) c = 0;
         else if (c >= MAX_LEVELS) c = MAX_LEVELS - 1;

         // Set point
         // x is the column; y is the row
         image[y, x] = (byte)c;
      }


      /// <summary>
      /// Returns the point's color index into the current palette.
      /// </summary>
      /// <param name="x">x position (column)</param>
      /// <param name="y">y position (row)</param>
      /// <returns>color index (grey scale)</returns>
      public byte GetColorIndex(int x, int y)
      {
         // Validate - don't throw exception or fail; just constrain
         if (x < 0) x = 0;
         else if (x >= MAX_WIDTH) x = MAX_WIDTH - 1;

         if (y < 0) y = 0;
         else if (y >= MAX_HEIGHT) y = MAX_HEIGHT - 1;

         // Get point
         // x is the column; y is the row
         return (image[y, x]);
      }


      /// <summary>
      /// Shifts all array cells up one row, wrapping top row to
      /// the bottom row.
      /// </summary>
      public void ShiftUp()
      {
         int row, col;
         byte[] swap = new byte[MAX_WIDTH];

         // Copy top row to swap array
         for (col = 0; col < MAX_WIDTH; col++)
            swap[col] = image[0, col];

         // Copy each row (after top) to previous row
         for (row = 1; row < MAX_HEIGHT; row++)
            for (col = 0; col < MAX_WIDTH; col++)
               image[row - 1, col] = image[row, col];

         // Restore swap row to bottom row
         for (col = 0; col < MAX_WIDTH; col++)
            image[MAX_HEIGHT - 1, col] = swap[col];
      }


      /// <summary>
      /// Shifts all array cells down one row, wrapping bottom row
      /// to the top row.
      /// </summary>
      public void ShiftDown()
      {
         int row, col;
         byte[] swap = new byte[MAX_WIDTH];

         // Copy bottom row to swap array
         for (col = 0; col < MAX_WIDTH; col++)
            swap[col] = image[MAX_HEIGHT - 1, col];

         // Copy previous row (except top) to current row (bottom to top)
         for (row = MAX_HEIGHT - 1; row > 0; row--)
            for (col = 0; col < MAX_WIDTH; col++)
               image[row, col] = image[row - 1, col];

         // Restore swap row to top row
         for (col = 0; col < MAX_WIDTH; col++)
            image[0, col] = swap[col];
      }


      /// <summary>
      /// Shifts all array cells left one column, wrapping the left 
      /// column to the right column
      /// </summary>
      public void ShiftLeft()
      {
         int row, col;
         byte[] swap = new byte[MAX_HEIGHT];

         // Copy first column to swap
         for (row = 0; row < MAX_HEIGHT; row++)
            swap[row] = image[row, 0];

         // Copy next column to current column (left to right)
         for (col = 1; col < MAX_WIDTH; col++)
            for (row = 0; row < MAX_HEIGHT; row++)
               image[row, col - 1] = image[row, col];

         // Restore swap ro to the first
         for (row = 0; row < MAX_HEIGHT; row++)
            image[row, MAX_WIDTH - 1] = swap[row];
      }


      /// <summary>
      /// Shifts all array cells right one column, wrapping the right
      /// column to the left column.
      /// </summary>
      public void ShiftRight()
      {
         int row, col;
         byte[] swap = new byte[MAX_HEIGHT];

         // Copy last column to swap
         for (row = 0; row < MAX_HEIGHT; row++)
            swap[row] = image[row, MAX_WIDTH - 1];

         // Copy previous column to current column (right to left)
         for (col = MAX_WIDTH - 1; col > 0; col--)
            for (row = 0; row < MAX_HEIGHT; row++)
               image[row, col] = image[row, col - 1];

         // Restore swap row to last (right) column
         for (row = 0; row < MAX_HEIGHT; row++)
            image[row, 0] = swap[row];
      }


      /// <summary>
      /// A horizontal flip reflects the image across the y (vertical) axis.
      /// </summary>
      public void FlipHorizontal()
      {
         // The flip is done by swapping opposite columns while approaching the middle
         for (int row = 0; row < MAX_HEIGHT; row++)
         {
            int left_col = 0;
            int right_col = MAX_WIDTH - 1;

            while (left_col < right_col)
            {
               // Swap the left with the right
               byte swap = image[row, left_col];
               image[row, left_col] = image[row, right_col];
               image[row, right_col] = swap;

               // Left moves up, right moves down to center of array
               left_col++;
               right_col--;
            } // while

         } // for row
      }


      /// <summary>
      /// A vertical flip reflects the image across the x (horizontal) axis.
      /// </summary>
      public void FlipVertical()
      {
         // The flip is done by swapping opposite rows while approaching the middle
         for (int col = 0; col < MAX_WIDTH; col++)
         {
            int top_row = 0;
            int bottom_row = MAX_HEIGHT - 1;

            while (top_row < bottom_row)
            {
               // Swap the top and the bottom
               byte swap = image[top_row, col];
               image[top_row, col] = image[bottom_row, col];
               image[bottom_row, col] = swap;

               // Top moves down, bottom moves up towards array center
               top_row++;
               bottom_row--;
            } // while

         } // for col

      }


      /// <summary>
      /// Rotates the image 90o.  To do 180, 270, or 90oCCW, just call
      /// this methods multiple times before repainting.
      /// </summary>
      public void RotateCW()
      {
         byte[,] source = (byte[,]) image.Clone();

         // To rotate 90o, move each row into the equivalent numbered column
         for (int row = 0; row < MAX_HEIGHT; row++)
            for (int col = 0; col < MAX_WIDTH; col++)
               image[row, col] = source[MAX_WIDTH - col - 1, row];
      }

      public void RotateCCW()
      {
         this.RotateCW();
         this.RotateCW();
         this.RotateCW();
      }

      public void Rotate180()
      {
         this.RotateCW();
         this.RotateCW();
      }


      /// <summary>
      /// Loads the image from a Base64 encoded string.
      /// </summary>
      /// <param name="encoded"></param>
      public void FromBase64(string encoded)
      {
         try
         {
            byte[] unrolled = Convert.FromBase64String(encoded);

            for (int row = 0; row < MAX_HEIGHT; row++)
               for (int col = 0; col < MAX_WIDTH; col++)
                  image[row, col] = unrolled[(row * MAX_HEIGHT) + col];
         }
         catch (Exception ex)
         {
            // If Base64 data was invalid, then clear image
            this.Clear();
         }

      }


      /// <summary>
      /// Overloaded ToString() provides ouput of array in different formats.
      /// </summary>
      /// <param name="type">Type of format to output</param>
      /// <returns>A formatted string of the requested type representing the image.</returns>
      public string ToString(FORMAT_TYPE outputType)
      {
         switch (outputType)
         {
            case FORMAT_TYPE.IMAGE_BASE64:
               return (ConvertToBase64());

            case FORMAT_TYPE.IMAGE_PLANAR:
               return (CovertToPlanar());

            case FORMAT_TYPE.IMAGE_RAW:
            default:
               return (ConvertToRaw());
         }
      }



      /*
       * 
       * PRIVATE METHODS
       * 
       * 
       */

      private string ConvertToRaw()
      {
         int row, col;
         string formatted;

         // Raw image definition
         formatted =
         "char image[25][25] =\r\n" +
         "{\r\n";

         // Data
         for (row = 0; row < MAX_HEIGHT; row++)
         {
            for (col = 0; col < MAX_WIDTH; col++)
            {
               if (col == 0)
                  formatted += "  {";

               formatted += image[row, col].ToString();

               if (col == 24)
                  formatted += "}";
               else
                  formatted += ", ";
            }

            // End of row delimiter
            if (row == 24)
               formatted += "\r\n";
            else
               formatted += ",\r\n";
         }

         // Done
         return (formatted);
      }


      private string CovertToPlanar()
      {
         byte[] bitplane = new byte[4] { 1, 2, 4, 8 };
         string formatted;

         // Planar image definition
         formatted =
            "uint32_t image[4][25] =\r\n" +
            "{\r\n";

         for (int plane = 0; plane < 4; plane++)
         {
            for (int row = 0; row < MAX_HEIGHT; row++)
            {
               byte[] rd = new byte[32];
               for (int k = 0; k < rd.Length; k++) rd[k] = 0;

               // Create the 32 bit value
               for (int col = 0; col < MAX_WIDTH; col++)
                  if ((image[row, col] & bitplane[plane]) == bitplane[plane])
                     rd[31 - col] = 1;

               if (row == 0)
                  formatted += "  {";

               // Create a string of the array (bits to binary string)
               string binary = "";
               for (int k = 0; k < rd.Length; k++)
                  binary += rd[k].ToString();

               // Convert binary string to unsigned int (32 bit) and convert it to 8 character hex
               formatted += "0x" + Convert.ToUInt32(binary, 2).ToString("X8");

               if (row == 24)
                  formatted += "}";
               else
                  formatted += ", ";
            }  // for row

            // End of plane delimiter
            if (plane == 3)
               formatted += "\r\n";
            else
               formatted += ",\r\n";
         }  // for plane

         return (formatted);
      }


      // Converts image data to Base 64
      private string ConvertToBase64()
      {
         byte[] unrolled = new byte[MAX_HEIGHT * MAX_WIDTH];

         // Unroll image
         for (int row = 0; row < MAX_HEIGHT; row++)
            for (int col = 0; col < MAX_WIDTH; col++)
               unrolled[(row * MAX_HEIGHT) + col] = image[row, col];

         // Return Base64 string
         return (Convert.ToBase64String(unrolled));
      }

   }

}
